
#include <cstdlib>
#include <math.h>
#include <time.h>

#include <wx/socket.h>
#include <wx/filename.h>
#include <wx/dir.h>
#include <wx/mimetype.h>

#include <curl/curl.h>

#include "sha1/sha1.h"

#include "Util.h"
#include "md5.h"
#include "Base64.h"

#include "MsnDef.h"
#include "GlobalConfig.h"

wxString StringUtil::md5(wxString& src, unsigned char* buf){
        MD5 md5;
        MD5_CTX ctx;

        md5.MD5Init(&ctx);
        md5.MD5Update(&ctx,WS2UC(src),src.Len());
        md5.MD5Final(buf,&ctx);

        return wxString::FromAscii((char*)buf);
}

wxString StringUtil::ToHex(unsigned char c)
{
	char b[3] = {0};
    sprintf(b,"%02x", c);
    return wxString::FromAscii(b);
}

wxString StringUtil::ToHex(wxString& str)
{
    wxCharBuffer cb = str.ToAscii();
    unsigned char* tmp = (unsigned char*)cb.data();
    return ToHex(tmp,str.Length());
}


wxString StringUtil::ToHex(unsigned char* buf, int len)
{
    wxString result;
	char b[3] = {0};
    for(int i=0;i<len;i++){
        //if(i>0 && i%16 == 0) result<<_T("\r\n");
        sprintf(b,"%02x",buf[i]);
		result<<wxString(b, wxConvUTF8, 2);
    }

    return result;
}

wxString StringUtil::ToHexFormat(unsigned char* buf, int len)
{
    wxString result;
	char b[4] = {0};
    for(int i=0;i<len;i++){
        if(i>0 && i%16 == 0) result<<_T("\r\n");
        sprintf(b, "%02x ", buf[i]);
		result<<wxString::FromAscii(b);
    }

    return result;
}

long StringUtil::ToLong(const wxString& value, int base)
{
    long tmp;
    value.ToLong(&tmp, base);
    return tmp;
}

wxString StringUtil::ToString(int value)
{
	return wxString::Format(_T("%d"), value);
}

wxString StringUtil::SubString(const wxString& src, const wxString& start, const wxString& end)
{
	int startPos = src.find(start);
    int endPos = src.find(end, startPos);

	endPos = endPos==-1?src.Length():endPos;

	return src.SubString(startPos+start.Length(), endPos-1);
}


void StringUtil::FromUTF16(char* src, int len, wxString& dest)
{
	wxMBConvUTF16 utf16;
	wxString str = wxString(src, utf16, len);
	dest = str;
}

void StringUtil::ToUTF16(wxString& src, char* dest)
{
	wxMBConvUTF16 utf16;
	wxCharBuffer buf = src.mb_str(utf16);
	memcpy(dest, buf, src.Length()*2); 
}


unsigned int StringUtil::ReadInt(char* buf, int offset, int len, bool littleEndian)
{
	unsigned int result = 0;
	int sw = 0;
	//little endian
	for(int i=0;i<len;i++){
		if(littleEndian){
			result |= ((unsigned int) buf[offset + i] & 0x00ff) << sw;
		}else{
			result |= ((unsigned int) buf[offset + len - i] & 0x00ff) << sw;
		}
		sw += 8;
	}

	return result;
}

unsigned long long StringUtil::ReadInt8(char* buf, int offset, int len)
{
	unsigned long long result = 0;
	int sw = 0;
	//little endian
	for(int i=0;i<len;i++){
		result |= ((unsigned long long) buf[offset + i] & 0x00ff) << sw;
		sw += 8;
	}

	return result;
}

void StringUtil::WriteInt(char* buf, int offset, unsigned int value, bool littleEndian)
{
	int sw = 0;
	for(int i=0; i<4; i++){
		if(littleEndian){
			buf[offset + i] = (value >> sw) & 0x00ff;
		}else{
			buf[offset + 3 - i] = (value >> sw) & 0x00ff;
		}
		sw += 8;
	}
}

void StringUtil::WriteInt8(char* buf, int offset, unsigned long long value)
{

	//memcpy(u.c, array, sizeof(long));
//	int size = sizeof(long long);
//	memcpy(buf, &value, 8);

	int sw = 0;
	//little endian
	for(int i=0; i<8; i++){

		buf[offset + i] = (value >> sw) & 0x00ff;
		sw += 8;
	}
	
}

int StringUtil::IndexOf(char* buf, int bufSize, char* target, int targetSize)
{
	if(targetSize > bufSize) return -1;

	int max = bufSize - targetSize;

	char t = target[0];

	for(int i=0; i < max; i++){
		if(buf[i] != target[0]) continue;

		int j=1;
		for( ; j<targetSize; j++){
			if(target[j] != buf[i+j]) break;
		}

		if(j == targetSize) return i;
	}

	return -1;
}

//(Size * 3) / 4 + 1
int StringUtil::Base64_Encode(char *text, char*buf, int size)
{
    static char *base64_encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    int buflen = 0;

    while(size>0){
        *buf++ = base64_encoding[ (text[0] >> 2 ) & 0x3f];
        if(size>2){
            *buf++ = base64_encoding[((text[0] & 3) << 4) | (text[1] >> 4)];
            *buf++ = base64_encoding[((text[1] & 0xF) << 2) | (text[2] >> 6)];
            *buf++ = base64_encoding[text[2] & 0x3F];
        }else{
            switch(size)
            {
            case 1:
            *buf++ = base64_encoding[(text[0] & 3) << 4 ];
            *buf++ = '=';
            *buf++ = '=';
            break;
            case 2:
            *buf++ = base64_encoding[((text[0] & 3) << 4) | (text[1] >> 4)];
            *buf++ = base64_encoding[((text[1] & 0x0F) << 2) | (text[2] >> 6)];
            *buf++ = '=';
            break;
            }
        }

        text +=3;
        size -=3;
        buflen +=4;
    }

    *buf = 0;
    return buflen;
}

int StringUtil::base64encode(unsigned char *input, int input_length, unsigned char *output)
{
	static char *basis_64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

	int     i = 0, j = 0;
	int     pad;
 	
	//assert(output_length >= (input_length * 4 / 3));
 	
	while (i < input_length) {
		pad = 3 - (input_length - i);
 	                if (pad == 2) {
 	                        output[j  ] = basis_64[input[i]>>2];
 	                        output[j+1] = basis_64[(input[i] & 0x03) << 4];
 	                        output[j+2] = '=';
 	                        output[j+3] = '=';
 	                } else if (pad == 1) {
 	                        output[j  ] = basis_64[input[i]>>2];
 	                        output[j+1] = basis_64[((input[i] & 0x03) << 4) | ((input[i+1] & 0xf0) >> 4)];
 	                        output[j+2] = basis_64[(input[i+1] & 0x0f) << 2];
 	                        output[j+3] = '=';
	                } else{
	                        output[j  ] = basis_64[input[i]>>2];
 	                        output[j+1] = basis_64[((input[i] & 0x03) << 4) | ((input[i+1] & 0xf0) >> 4)];
 	                        output[j+2] = basis_64[((input[i+1] & 0x0f) << 2) | ((input[i+2] & 0xc0) >> 6)];
 	                        output[j+3] = basis_64[input[i+2] & 0x3f];
 	                }
 	                i += 3;
 	                j += 4;
 	}

	return j;
}


char StringUtil::Base64_Value(char ch)
{
    if ((ch >= 'A') && (ch <= 'Z'))
        return ch - 'A';
    if ((ch >= 'a') && (ch <= 'z'))
        return ch - 'a' + 26;
    if ((ch >= '0') && (ch <= '9'))
        return ch - '0' + 52;
    switch (ch){
        case '+':
            return 62;
        case '/':
            return 63;
        case '=': /* base64 padding */
            return 0;
        default:
            return 0;
    }
}

int StringUtil::Base64_Decode(char *text, char*buf, int size)
{
    if(size%4) return -1;

    unsigned char chunk[4];
    int parsenum=0;

    while(size > 4){
        chunk[0] = Base64_Value(text[0]);
        chunk[1] = Base64_Value(text[1]);
        chunk[2] = Base64_Value(text[2]);
        chunk[3] = Base64_Value(text[3]);

        *buf++ = (chunk[0] << 2) | (chunk[1] >> 4);
        *buf++ = (chunk[1] << 4) | (chunk[2] >> 2);
        *buf++ = (chunk[2] << 6) | (chunk[3]);

        text+=4;
        size-=4;
        parsenum+=3;
    }

	chunk[0] = Base64_Value(text[0]);
        chunk[1] = Base64_Value(text[1]);
        chunk[2] = Base64_Value(text[2]);
        chunk[3] = Base64_Value(text[3]);

	if(text[3] != '=') {   // no pad
	  *buf++ = (chunk[0] << 2) + (chunk[1] >> 4);
	  *buf++ = (chunk[1] << 4) + (chunk[2] >> 2);
	  *buf++ = (chunk[2] << 6) + chunk[3];
	  parsenum += 3;
	} else if(text[2] != '=') {  // two pad
	  *buf++ = (chunk[0] << 2) + (chunk[1] >> 4);
	  *buf++ = (chunk[1] << 4) + (chunk[2] >> 2);
	  parsenum += 2;
	} else if(text[1] != '=') {    // one pads
	  *buf++ = (chunk[0] << 2) + (chunk[1] >> 4);
	  *buf++ = (chunk[1] << 4);
	  parsenum += 1;
	}

    return parsenum;
}

void StringUtil::Base64_Decode(const wxString& src, wxString& dest)
{
    int len = WSLEN(src);
    char* buf = new char[len];

    //int destLen = Base64_Decode(WS2C(src), buf, len);
	//dest = wxString(buf, wxConvUTF8, destLen);
	//delete []buf;

	Base64 base64;
	base64.decode(WS2C(src), buf);

	dest = wxString(buf, wxConvUTF8, strlen(buf));
	delete []buf;
}

void StringUtil::Base64_Encode(const wxString& src, wxString& dest)
{
    int len = WSLEN(src) ;
	int newLen = ceil((double)len/3)*4 + 1;
    char* buf = new char[newLen];

    //int destLen = Base64_Encode(WS2C(src), buf, len);
	//dest << wxString(buf, wxConvUTF8, destLen);
	//delete []buf;

	Base64 base64;
	base64.encode(WS2C(src), buf);

	//base64encode(WS2UC(src), WSLEN(src), (unsigned char*)buf);

	dest = wxString(buf, wxConvUTF8, strlen(buf));
	delete []buf;
}


void StringUtil::URLDecode(const wxString& src, wxString& dest)
{
    int len = WSLEN(src);
	char* buf = new char[len];

    int destLen = URLDecode(WS2C(src), buf, len);

	dest = wxString(buf,wxConvUTF8, destLen);

	delete[] buf;
}

int StringUtil::URLDecode(char* src, char* dest,int len)
{
        int i,j;
        for(i=0, j=0;i<len;i++){
            if(src[i] == '%'){
                if(src[i+1]>='a' && src[i+1]<='f') dest[j]=src[i+1]- 'a'+10;
                else if(src[i+1]>='A' && src[i+1]<='F') dest[j]=src[i+1]-'A'+10;
                else if(src[i+1]>='0' && src[i+1]<='9') dest[j]=src[i+1]-'0';
                else {
                    dest[j] = '%';
                    dest[j+1] = src[i+1];
                    j+=2;
                    i+=1;
                    continue;
                }

                dest[j]*=16;

                if(src[i+2]>='a' && src[i+2]<='f') dest[j]+=src[i+2]-'a'+10;
                else if(src[i+2] >='A' && src[i+2]<='F')dest[j]+=src[i+2]-'A'+10;
                else if(src[i+2] >='0' && src[i+2]<='9') dest[j]+=src[i+2]-'0';
                else{
                    dest[j]='%';
                    dest[j+1]=src[i+1];
                    dest[j+2]=src[i+2];
                    j+=3;
                    i+=2;
                    continue;
                }
                j+=1;
                i+=2;
            }else{
                dest[j++]=src[i];
            }
        }
    return j;
}

void StringUtil::URLEncode(const wxString& src, wxString& dest)
{
    int len = WSLEN(src);
    char* buf = new char[len*3];
    memset(buf, 0, len*3);

    int destLen = URLEncode(WS2C(src), buf, len);
	dest = wxString(buf,wxConvUTF8,destLen);
	delete []buf;
}

int StringUtil::URLEncode(char* src,char* dest,int len)
{
    char buf[3];
    int n=0;
    for(int i=0; i<len; i++){
        unsigned char c = src[i];
        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
             c == '.' || c == '-' || c == '_'){
            dest[n++] = c;
        }else{
            memset(buf,0,3);
            sprintf(buf, "%02x", c);
            //wxMessageBox(wxString::FromAscii(buf));
            dest[n++] = '%';
            //dest[n++] = c;
            dest[n++] = buf[0];
            dest[n++] = buf[1];
        }
    }

    return n;
}

wxString StringUtil::URLDecode(const wxString& src)
{
	wxString result;
	URLDecode(src, result);
	return result;
}

wxString StringUtil::URLEncode(const wxString& src)
{
	wxString result;
	URLEncode(src, result);
	return result;
}

int NumberUtil::RandInt()
{
	srand( (unsigned)time( NULL));
	return rand(); 
}

int NumberUtil::RandInt(int min, int max)
{
	return min + rand() % (max - min);
}

int NumberUtil::B2KB(int bytes)
{
	return bytes <= 1024 ? 1 : bytes / 1024 ;
}


bool FileUtil::MkDir(const wxString& path)
{
    if(wxDir::Exists(path)) return false;

    return wxFileName::Mkdir(path, 0777, wxPATH_MKDIR_FULL);
}

void FileUtil::OpenFileWIthDefaultApp(const wxString& path)
{
	if(wxFileName::FileExists(path)){

		wxMimeTypesManager manager;
		wxFileType *filetype=manager.GetFileTypeFromExtension(path.AfterLast('.'));
		wxString command=filetype->GetOpenCommand(path);
		
		wxExecute(command); 

		wxDELETE(filetype);
	}
}

bool FileUtil::DeleteFile(const wxString& path)
{
	if(wxFileName::FileExists(path)){
		return ::wxRemoveFile(path);
	}
	return false;
}


wxString SystemUtil::GetLocalAddress()
{
        wxIPV4address addr;
        addr.Hostname(wxGetFullHostName());
        return addr.IPAddress();
}


int SystemUtil::GetIdleTime()
{

#ifdef __WXMSW__
	LASTINPUTINFO info;
	info.cbSize = sizeof(LASTINPUTINFO);
	GetLastInputInfo(&info);

	return (GetTickCount()-info.dwTime)/1000;

#else

	//TODO: fix me on other platform
	return 0;
#endif
}

void SecurityUtil::Sha1(const wxFileName& fileNmae, unsigned char* digestBuf)
{
	sha1_context sha1Context;
	sha1_starts(&sha1Context);
	wxFile sha1file(fileNmae.GetFullPath(), wxFile::read);
	unsigned char buffer[16384];
	unsigned int i = 1;
	while (i >0)
	{
		i = sha1file.Read(buffer,16384);
		sha1_update(&sha1Context, buffer, (unsigned) i);
	}
    sha1_finish(&sha1Context, digestBuf);	
}



wxString MsnUtil::Sha1D(const wxFileName& fileName)
{
	unsigned char buf[20] = {0};
	unsigned char dest[100] = {0};
	SecurityUtil::Sha1(fileName, buf);
	StringUtil::base64encode(buf, 20, dest);

	return wxString::FromAscii((char*)dest);	
}

wxString MsnUtil::CreateMsnObject(const wxFileName& fileName, const wxString& passport, int fileType)
{
	//wxString result;

	wxString msnObject;
	msnObject << _T("<msnobj Creator=\"") << passport << _T("\" ");
	msnObject << _T("Type=\"") << fileType << _T("\" ");

	unsigned char buf[20] = {0};
	unsigned char dest[100] = {0};
	SecurityUtil::Sha1(fileName, buf);
	StringUtil::base64encode(buf, 20, dest);

	msnObject << _T("SHA1D=\"") << wxString::FromAscii((char*)dest) << _T("\" ");
	msnObject << _T("Size=\"") << fileName.GetSize() << _T("\" ");
	msnObject << _T("Location=\"") << _T("0") << _T("\" ");
	msnObject << _T("Friendly=\"") << _T("AAA=") << _T("\"/>");

	return msnObject;
}


size_t HttpUtil::processHeader(void *ptr, size_t size, size_t nmemb, void *stream)
{
    wxString* pStr = (wxString*) stream;

    wxString tmp = wxString::FromAscii((char*)ptr);

    if(tmp.StartsWith(_T("Authentication-Info"))){
        int from = tmp.Find(_T("from-PP="));
        if(from != -1){
            //int start = tmp.Find(_("'"));
            //int end = tmp.Find(_("'"));
            *pStr = tmp.AfterFirst('\'').BeforeLast('\'');
        }
    }

    return size*nmemb;
}

wxString HttpUtil::GetTicket(const wxString& info, const wxString& account, const wxString& password)
{
    wxString passport;
    CURL *curl = curl_easy_init();

    if(curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://loginnet.passport.com/login2.srf");

        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);
        curl_easy_setopt(curl, CURLOPT_HEADER , 1);
        //curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)");
        //curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);//

        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);


		//set proxy

		int connectionType = GlobalConfig::GetConnectionType();
		
		if(connectionType == MSN_CONNECTION_HTTP_PROXY){
			wxString proxyAddress = GlobalConfig::GetHttpProxyAddress();
			int proxyPort = GlobalConfig::GetHttpProxyPort();
			wxString userPwd = GlobalConfig::GetHttpProxyUsername() + _T(":") + GlobalConfig::GetHttpProxyPassword();

			wxCharBuffer addrChar = proxyAddress.ToAscii();
			curl_easy_setopt(curl, CURLOPT_PROXY, (unsigned char*)addrChar.data());	
			curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxyPort);
		
			wxCharBuffer userChar = userPwd.ToAscii();
			curl_easy_setopt(curl, CURLOPT_USERPWD, (unsigned char*)userChar.data());	
		}


        //curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);

        wxString host = _T("Host:loginnet.passport.com");
        wxString auth = _T("Authorization:Passport1.4 OrgVerb=GET,OrgURL=http://messenger.msn.com,sign-in=") + account + _T(",pwd=") + password + _T(",")+info;

        struct curl_slist *slist=NULL;
        slist = curl_slist_append(slist, host.mb_str());
        slist = curl_slist_append(slist, auth.mb_str());
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);

        curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &passport);
        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, processHeader);

        curl_easy_perform(curl);

        curl_slist_free_all(slist);
        curl_easy_cleanup(curl);

        return passport;
    }

    return wxString();
}

size_t HttpUtil::write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
    wxMemoryBuffer* buffer = (wxMemoryBuffer*) stream;

    //*pStr = wxString::FromAscii((char*)ptr);
	buffer->AppendData(ptr, nmemb);

    return size*nmemb;
}

wxString HttpUtil::TestPost(const wxString& cmd)
{
	wxString encodedCmd = StringUtil::URLEncode(cmd);


	wxCharBuffer cb = cmd.ToAscii();
    unsigned char* tmp = (unsigned char*)cb.data();

	

    CURL *curl = curl_easy_init();

    if(curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "http://gateway.messenger.hotmail.com/gateway/gateway.dll?Action=open&Server=NS&IP=messenger.hotmail.com");

		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, tmp);


		//http proxy server
		//curl_easy_setopt(curl, CURLOPT_PROXY, "198.24.9.249");	
		//curl_easy_setopt(curl, CURLOPT_PROXYPORT, 8080);

		//curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, WSLEN(cmd));

		wxMemoryBuffer buffer;

		curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &buffer);
        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, write_data);

		//curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
		//curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
       
        CURLcode response = curl_easy_perform(curl);

		switch(response)
         {

                case CURLE_OK:
					return wxString((char*)buffer.GetData(), wxConvUTF8, buffer.GetDataLen());
                case CURLE_HTTP_POST_ERROR:
                        return _T("post error");
                case CURLE_COULDNT_CONNECT:
                        return _T("cannot connect to server");

                }

        curl_easy_cleanup(curl);

    }

    return wxString(_T("unknown"));	
}

